﻿// ----------------------------------------------------------------------------------------
// Author:                    Magaly Vargas y Joaquín López
// Company:                   ALBALIA INTERACTIVA S.L.
// Assembly version:          1.0.0.0
// Date:                      22/02/2010
// Time:                      12:00
// Solution Name:             OffInvoiceWordAddIn
// Solution Filename:         OffInvoiceWordAddIn
// Solution FullFilename:     C:\Desarrollo\OffInvoiceWordAddIn
// Project Name:              OffInvoiceWordAddIn
// Project Filename:          OffInvoiceWordAddIn.csproj
// Project FullFilename:      C:\Desarrollo\OffInvoiceWordAddIn\OffInvoiceWordAddIn.csproj
// Project Item Name:         CIIValidation.cs
// Project Item Filename:     CIIValidation.cs
// Project Item FullFilename: C:\Desarrollo\OffInvoiceWordAddIn\CIIFiles\CIIValidation.cs
// Project Item Kind:         Código
// Purpose:                   
//Copyright © 2009 - 2010 Albalia Interactiva S.L. Reservados todos los derechos.
// ----------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xades;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.IO;
using System.Globalization;

namespace OffInvoiceWordAddIn.CIIFiles
{

    /// <summary>
    /// Clase que contiene los método para realizar la validación de una factura CII
    /// </summary>
    class CIIValidation
    {

        /// <summary>
        /// Método estático que valida el esquema de la factura.
        /// </summary>
        /// <param name="doc">Documento XML con la factura CII</param>
        /// <returns>Resultados de la validacion</returns>
        public static ResultCIIValidation SchemaValidation(XmlDocument doc)
        {
            ResultCIIValidation result = new ResultCIIValidation();
            result.Success = true;
            result.MsgError = "";
            try
            {
                XmlSchemaSet schemas = new XmlSchemaSet();
                schemas.Add("urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:1", XmlReader.Create(Globals.ThisAddIn.EnvironmentVariable + Paths.CIISchemas));

                XDocument doc1 = XDocument.Parse(doc.OuterXml);

                doc1.Validate(schemas, (o, e) =>
                {
                    result.Success = false;
                    result.MsgError += e.Message + "\n-";
                });
            }
            catch (Exception ex)
            {
                result.Success = false;
                result.MsgError += ex.Message + "\n-";
            }
            return result;
        }

        /// <summary>
        /// Método estático que se encarga de la validación contable de la factura
        /// </summary>
        /// <param name="doc">Documento XML con la factura</param>
        /// <returns>Resultados de la validacion</returns>
        public static ResultCIIValidation AccountValidation(XmlDocument doc)
        {
            CultureInfo culture = new CultureInfo("en-US");
            NumberFormatInfo num = culture.NumberFormat;
            ResultCIIValidation result = new ResultCIIValidation();
            result.Success = true;
            result.MsgError = "";

            double tTotal = 0;
            double tTotalIVA = 0;
            double tTotalIRPF = 0;
            double tTotalDescuento = 0;
            double tTotalBaseImponible = 0;
            Dictionary<int, double> listaIVA = new Dictionary<int, double>();
            Dictionary<int, double> listaIRPF = new Dictionary<int, double>();
            Dictionary<int, double> listaDescuento = new Dictionary<int, double>();
            double cantidad = 0;
            double precioPorUnidad = 0;
            double baseImponible = 0;
            double descuentoImporte = 0;
            double descuentoPorcentaje = 0;
            double ivaImporte = 0;
            double ivaPorcentaje = 0;
            double irpfImporte = 0;
            double irpfPorcentaje = 0;

            XmlSerializer serializer = new XmlSerializer(typeof(CrossIndustryInvoice));
            XmlReader r = new XmlTextReader(new StringReader(doc.DocumentElement.OuterXml));
            CrossIndustryInvoice factura = (CrossIndustryInvoice)serializer.Deserialize(r);
            r.Close();

            for (int i = 0; i < factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem.Count; i++)
            {
                cantidad = double.Parse(factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeDelivery.SpecifiedCIDeliveryAdjustment.ActualQuantity.Value, num);
                precioPorUnidad = double.Parse(factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeDelivery.SpecifiedCIDeliveryAdjustment.ActualAmount.Value, num);

                //TODO: Hay que buscar un campo para la Base imponible
                //baseImponible = double.Parse(cambiarPuntosPorComas(factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].LineExtensionAmount.Value));

                baseImponible = cantidad * precioPorUnidad;

                if (factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeSettlement.SpecifiedCITradeAllowanceCharge != null)
                {
                    for (int j = 0; j < factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeSettlement.SpecifiedCITradeAllowanceCharge.Count; j++)
                    {
                        if (factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeSettlement.SpecifiedCITradeAllowanceCharge[j].ReasonCode.Value.Equals("2"))
                        {

                            descuentoPorcentaje = double.Parse(factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeSettlement.SpecifiedCITradeAllowanceCharge[j].CalculationPercent.Value, num);
                            descuentoImporte = double.Parse(factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeSettlement.SpecifiedCITradeAllowanceCharge[j].ActualAmount.Value, num);
                            int d = Convert.ToInt32(descuentoPorcentaje);
                            if (listaDescuento.ContainsKey(d))
                            {
                                listaDescuento[d] += descuentoImporte;
                            }
                            else
                            {
                                listaDescuento.Add(d, descuentoImporte);
                            }
                            baseImponible -= descuentoImporte;
                        }
                    }
                }

                if (factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeSettlement.ApplicableCITradeTax != null)
                {
                    for (int j = 0; j < factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeSettlement.ApplicableCITradeTax.Count; j++)
                    {
                        if (factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeSettlement.ApplicableCITradeTax[j].CategoryCode.Value.Equals("VAT"))
                        {
                            ivaPorcentaje = double.Parse(factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeSettlement.ApplicableCITradeTax[j].CalculatedRate.Value, num);
                            ivaImporte = double.Parse(factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeSettlement.ApplicableCITradeTax[j].CalculatedAmount.Value, num);
                            int p = Convert.ToInt32(ivaPorcentaje);
                            if (listaIVA.ContainsKey(p))
                            {
                                listaIVA[p] += ivaImporte;
                            }
                            else
                            {
                                listaIVA.Add(p, ivaImporte);
                            }
                        }
                        else if (factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeSettlement.ApplicableCITradeTax[j].CategoryCode.Value.Equals("IRPF"))
                        {
                            irpfPorcentaje = double.Parse(factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeSettlement.ApplicableCITradeTax[j].CalculatedRate.Value, num);
                            irpfImporte = double.Parse(factura.CIIHSupplyChainTradeTransaction.IncludedCIILSupplyChainTradeLineItem[i].SpecifiedCIILSupplyChainTradeSettlement.ApplicableCITradeTax[j].CalculatedAmount.Value, num);
                            int p = Convert.ToInt32(irpfPorcentaje);
                            if (listaIRPF.ContainsKey(p))
                            {
                                listaIRPF[p] += irpfImporte;
                            }
                            else
                            {
                                listaIRPF.Add(p, irpfImporte);
                            }
                        }
                    }
                }

                if (!(Round3((((cantidad * precioPorUnidad) * descuentoPorcentaje) / 100), 2).Equals(descuentoImporte)))
                {
                    result.Success = false;
                    result.MsgError = "ErrorDiscountLine";
                    return result;
                }
                if (!(Round3(((cantidad * precioPorUnidad) - descuentoImporte), 2).Equals(baseImponible)))
                {
                    result.Success = false;
                    result.MsgError = "ErrorBaseAmountLine";
                    return result;
                }
                if (!(Round3(((baseImponible * ivaPorcentaje) / 100), 2).Equals(ivaImporte)))
                {
                    result.Success = false;
                    result.MsgError = "ErrorTaxLine";
                    return result;
                }
                if (!(Round3(((baseImponible * irpfPorcentaje) / 100), 2).Equals(irpfImporte)))
                {
                    result.Success = false;
                    result.MsgError = "ErrorIRPFLine";
                    return result;
                }
                tTotalBaseImponible += baseImponible;
                tTotalDescuento += descuentoImporte;
                tTotalIRPF += irpfImporte;
                tTotalIVA += ivaImporte;
                tTotal += (baseImponible + ivaImporte - irpfImporte);
            }

            double totalDescuentosGenerales = 0;
            for (int a = 0; a < factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.SpecifiedCITradeAllowanceCharge.Count; a++)
            {
                if (factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.SpecifiedCITradeAllowanceCharge[a].ReasonCode.Value.Equals("1"))
                {
                    totalDescuentosGenerales += double.Parse(factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.SpecifiedCITradeAllowanceCharge[a].ActualAmount.Value, num);
                }
                else if (factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.SpecifiedCITradeAllowanceCharge[a].ReasonCode.Value.Equals("2"))
                {

                    descuentoPorcentaje = double.Parse(factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.SpecifiedCITradeAllowanceCharge[a].CalculationPercent.Value, num);
                    descuentoImporte = double.Parse(factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.SpecifiedCITradeAllowanceCharge[a].ActualAmount.Value, num);
                    int d = Convert.ToInt32(descuentoPorcentaje);
                    if (listaDescuento.ContainsKey(d))
                    {
                        if (!Round3(listaDescuento[d], 2).Equals(descuentoImporte))
                        {
                            result.Success = false;
                            result.MsgError = "ErrorGlobalDiscount";
                            return result;
                        }
                    }
                    else
                    {
                        result.Success = false;
                        result.MsgError = "ErrorGlobalDiscount";
                        return result;
                    }
                }
            }

            for (int t = 0; t < factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.ApplicableCITradeTax.Count; t++)
            {
                if (factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.ApplicableCITradeTax[t].CategoryCode.Value.Equals("VAT"))
                {
                    ivaPorcentaje = double.Parse(factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.ApplicableCITradeTax[t].CalculatedRate.Value, num);
                    ivaImporte = double.Parse(factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.ApplicableCITradeTax[t].CalculatedAmount.Value, num);
                    int p = Convert.ToInt32(ivaPorcentaje);
                    if (listaIVA.ContainsKey(p))
                    {
                        if (!Round3(listaIVA[p], 2).Equals(ivaImporte))
                        {
                            result.Success = false;
                            result.MsgError = "ErrorGlobalVAT";
                            return result;
                        }
                    }
                    else
                    {
                        result.Success = false;
                        result.MsgError = "ErrorGlobalVAT";
                        return result;
                    }
                }
                else if (factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.ApplicableCITradeTax[t].CategoryCode.Value.Equals("IRPF"))
                {
                    irpfPorcentaje = double.Parse(factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.ApplicableCITradeTax[t].CalculatedRate.Value, num);
                    irpfImporte = double.Parse(factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.ApplicableCITradeTax[t].CalculatedAmount.Value, num);
                    int p = Convert.ToInt32(irpfPorcentaje);
                    if (listaIRPF.ContainsKey(p))
                    {
                        if (!Round3(listaIRPF[p], 2).Equals(irpfImporte))
                        {
                            result.Success = false;
                            result.MsgError = "ErrorGlobalIRPF";
                            return result;
                        }
                    }
                    else
                    {
                        result.Success = false;
                        result.MsgError = "ErrorGlobalIRPF";
                        return result;
                    }
                }
            }

            if (!Round3(totalDescuentosGenerales, 2).Equals(double.Parse(factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.SpecifiedCIIHTradeSettlementMonetarySummation.TotalDiscountAmount.Value, num)))
            {
                result.Success = false;
                result.MsgError = "ErrorTotalGeneralDiscounts";
                return result;
            }
            if (!Round3(tTotalIVA, 2).Equals(double.Parse(factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.SpecifiedCIIHTradeSettlementMonetarySummation.TaxTotalAmount.Value, num)))
            {
                result.Success = false;
                result.MsgError = "ErrorTotalTaxOutputs";
                return result;
            }
            if (!Round3(tTotalBaseImponible, 2).Equals(double.Parse(factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.SpecifiedCIIHTradeSettlementMonetarySummation.GrandTotalAmount.Value, num)))
            {
                result.Success = false;
                result.MsgError = "ErrorTotalGrossAmountBeforeTaxes";
                return result;
            }
            if (!(Round3((tTotalBaseImponible - totalDescuentosGenerales), 2)).Equals(double.Parse(factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.SpecifiedCIIHTradeSettlementMonetarySummation.RoundingAmount.Value, num)))
            {
                result.Success = false;
                result.MsgError = "ErrorSumGrossAmount";
                return result;
            }
            if (!(Round3((tTotal - totalDescuentosGenerales), 2)).Equals(double.Parse(factura.CIIHSupplyChainTradeTransaction.ApplicableCIIHSupplyChainTradeSettlement.SpecifiedCIIHTradeSettlementMonetarySummation.TotalPrepaidAmount.Value, num)))
            {
                result.Success = false;
                result.MsgError = "ErrorInvoiceTotal";
                return result;
            }

            return result;
        }


        public static double Round3(double numero, int decimales)
        {
            double multi = Math.Pow(10.0d, (double)decimales);

            if (numero < 0)
                return (-(long)((-numero * multi) + 0.5d)) / multi;
            else
                return ((long)((numero * multi) + 0.5d)) / multi;

        }

        public static ResultCIIValidation SignedValidation(XmlDocument doc)
        {
            ResultCIIValidation result = new ResultCIIValidation();
            try
            {
                XadesSignature xadesSignature = new XadesSignature();
                xadesSignature.XadesValidation(doc, false);
                result.Success = true;
                result.MsgError = "";
            }
            catch (Exception ex)
            {
                result.Success = false;
                result.MsgError = ex.Message;
            }
            return result;
        }
    }

    /// <summary>
    /// Clase que contiene los resultados de una validación
    /// </summary>
    public class ResultCIIValidation
    {
        #region "Properties"

        /// <summary>
        /// Propiedad que indica si la validación ha sido correcta o no
        /// </summary>
        public bool Success { get; set; }

        /// <summary>
        /// Propiedad que almacena el mensaje de error si existiera
        /// </summary>
        public String MsgError { get; set; }

        #endregion
    }
}
